#include <QtGui>

#include "../inc/surfaceprojector.h"

#include <common/interfaces.h>
#if VERSION == 131
#include <vcg/complex/algorithms/create/platonic.h>
#include <vcg/complex/algorithms/point_sampling.h>
#elif VERSION == 133
#include <vcg/complex/algorithms/create/platonic.h>
#include <vcg/complex/algorithms/point_sampling.h>
#else
#include <vcg/complex/trimesh/create/platonic.h>
#include <vcg/complex/trimesh/point_sampling.h>
#endif
#include <vcg/space/index/grid_static_ptr.h>

using namespace vcg;

class RedetailSampler
{
    typedef vcg::GridStaticPtr<CMeshO::FaceType, CMeshO::ScalarType > MetroMeshGrid;
    typedef vcg::GridStaticPtr<CMeshO::VertexType, CMeshO::ScalarType > VertexMeshGrid;

public:

    RedetailSampler()
    {
        m=0;
    };

    CMeshO *m;           /// the source mesh for which we search the closest points (e.g. the mesh from which we take colors etc).
    CallBackPos *cb;
    int sampleNum;  // the expected number of samples. Used only for the callback
    int sampleCnt;
    MetroMeshGrid   unifGridFace;
    VertexMeshGrid   unifGridVert;
    bool useVertexSampling;

    // Parameters
    typedef tri::FaceTmark<CMeshO> MarkerFace;
    MarkerFace markerFunctor;

    bool coordFlag;
    bool colorFlag;
    bool normalFlag;
    bool qualityFlag;
    bool selectionFlag;
    bool storeDistanceAsQualityFlag;
    float dist_upper_bound;
    void init(CMeshO *_m, CallBackPos *_cb=0, int targetSz=0)
    {
        coordFlag=false;
        colorFlag=false;
        qualityFlag=false;
        selectionFlag=false;
        storeDistanceAsQualityFlag=false;
        m=_m;
        if(m)
        {
#if VERSION == 133
            tri::UpdateNormal<CMeshO>::PerFaceNormalized(*m);
#elif VERSION == 131
            tri::UpdateNormals<CMeshO>::PerFaceNormalized(*m);
#endif
            if(m->fn==0) useVertexSampling = true;
            else useVertexSampling = false;

            if(useVertexSampling) unifGridVert.Set(m->vert.begin(),m->vert.end());
            else  unifGridFace.Set(m->face.begin(),m->face.end());
            markerFunctor.SetMesh(m);
        }
        // sampleNum and sampleCnt are used only for the progress callback.
        cb=_cb;
        sampleNum = targetSz;
        sampleCnt=0;

    }
    // this function is called for each vertex of the target mesh.
    // and retrieve the closest point on the source mesh.
    void AddVert(CMeshO::VertexType &p)
    {
        // the results
        Point3f       closestPt;
        float dist = dist_upper_bound;
        const CMeshO::CoordType &startPt= p.cP();
        // compute distance between startPt and the mesh S2
        if(useVertexSampling)
        {
            CMeshO::VertexType   *nearestV=0;
            nearestV =  tri::GetClosestVertex<CMeshO,VertexMeshGrid>(*m,unifGridVert,startPt,dist_upper_bound,dist); //(PDistFunct,markerFunctor,startPt,dist_upper_bound,dist,closestPt);
            if(cb) cb(sampleCnt++*100/sampleNum,"Resampling Vertex attributes");
            if(storeDistanceAsQualityFlag)  p.Q() = dist;
            if(dist == dist_upper_bound) return ;

            if(coordFlag) p.P()=nearestV->P();
            if(colorFlag) p.C() = nearestV->C();
            if(normalFlag) p.N() = nearestV->N();
            if(qualityFlag) p.Q()= nearestV->Q();
            if(selectionFlag) if(nearestV->IsS()) p.SetS();
        }
        else
        {
            CMeshO::FaceType   *nearestF=0;
            vcg::face::PointDistanceBaseFunctor<CMeshO::ScalarType> PDistFunct;
            dist=dist_upper_bound;
            if(cb) cb(sampleCnt++*100/sampleNum,"Resampling Vertex attributes");
            nearestF =  unifGridFace.GetClosest(PDistFunct,markerFunctor,startPt,dist_upper_bound,dist,closestPt);
            if(dist == dist_upper_bound) return ;

            Point3f interp;
            vcg::InterpolationParameters(*nearestF,(*nearestF).cN(),closestPt, interp);
            interp[2]=1.0-interp[1]-interp[0];

            //if(coordFlag) p.P()=closestPt;
            if(colorFlag) p.C().lerp(nearestF->V(0)->C(),nearestF->V(1)->C(),nearestF->V(2)->C(),interp);
            if(normalFlag) p.N() = nearestF->V(0)->N()*interp[0] + nearestF->V(1)->N()*interp[1] + nearestF->V(2)->N()*interp[2];
            if(qualityFlag) p.Q()= nearestF->V(0)->Q()*interp[0] + nearestF->V(1)->Q()*interp[1] + nearestF->V(2)->Q()*interp[2];
            if(selectionFlag) if(nearestF->IsS()) p.SetS();
            p.Q() = dist;
        }
    }
};

surfaceProjector::surfaceProjector()
{
}

surfaceProjector::run(MeshModel* srcMesh,MeshModel* trgMesh, vcg::CallBackPos *cb){
    // srcMesh : mesh whose attribute are read
    // trgMesh : this whose surface is sought for the closest point to each sample.
    float upperbound = 1; // maximum distance to stop search
    srcMesh->updateDataMask(MeshModel::MM_FACEMARK);
#if VERSION == 133
    tri::UpdateNormal<CMeshO>::PerFaceNormalized(srcMesh->cm);
#elif VERSION == 131
    tri::UpdateFlags<CMeshO>::FaceProjection(srcMesh->cm);
#endif

    RedetailSampler rs;
    rs.init(&(srcMesh->cm),cb,trgMesh->cm.vn);

    rs.dist_upper_bound = upperbound;
    rs.colorFlag=false;
    rs.coordFlag=true;
    rs.normalFlag=false;
    rs.qualityFlag=false;
    rs.selectionFlag=false;

    rs.storeDistanceAsQualityFlag=true;

    if(!rs.colorFlag && !rs.coordFlag && !rs.qualityFlag  && !rs.normalFlag && !rs.selectionFlag)
    {
        //Log("You have to choose at least one attribute to be sampled");
        return false;
    }

    if(rs.colorFlag) {
        trgMesh->updateDataMask(MeshModel::MM_VERTCOLOR);
    }

    if(rs.qualityFlag){
        trgMesh->updateDataMask(MeshModel::MM_VERTQUALITY);
    }

    qDebug("Source  mesh has %7i vert %7i face",srcMesh->cm.vn,srcMesh->cm.fn);
    qDebug("Target  mesh has %7i vert %7i face",trgMesh->cm.vn,trgMesh->cm.fn);

    tri::SurfaceSampling<CMeshO,RedetailSampler>::VertexUniform(trgMesh->cm,rs,trgMesh->cm.vn);

    if(rs.coordFlag)
    {
#if VERSION == 133
        tri::UpdateNormal<CMeshO>::PerFaceNormalized(trgMesh->cm);
#elif VERSION == 131
        tri::UpdateNormals<CMeshO>::PerFaceNormalized(trgMesh->cm);
#endif
    }
}
